package zdream.rockchronicle.core.graphics;

import com.badlogic.gdx.graphics.Color;
import com.badlogic.gdx.graphics.GL20;
import com.badlogic.gdx.graphics.Texture;
import com.badlogic.gdx.graphics.g2d.Sprite;
import com.badlogic.gdx.graphics.g2d.SpriteBatch;
import com.badlogic.gdx.graphics.g2d.TextureRegion;
import com.badlogic.gdx.utils.ObjectMap;

import java.util.Objects;

import zdream.control.assets.TextureDescription;
import zdream.control.graphics.IPainter;
import zdream.control.world.Ticker;
import zdream.control.assets.TextureSelect;
import zdream.rockchronicle.core.textures.TextureManager;
import zdream.rockchronicle.core.textures.Textures;

/**
 * @author Zdream
 * @since 0.0.1
 * @date 2022-05-28
 */
public abstract class SpritePainter implements IPainter {
	public SpritePainter(TextureDescription[] descriptions, Ticker ticker) {
		this.textures = Objects.requireNonNull(descriptions);
		this.ticker = Objects.requireNonNull(ticker);
		select.setSheet(textures[0]);
		// 没有设定位置, 所以默认是 0, 0
		sprite = new Sprite();
	}

	public final Ticker ticker;
	private final Sprite sprite;
	public TextureDescription[] textures;
	protected ObjectMap<TextureDescription.Region, TextureRegion> regionMap = new ObjectMap<>();
	protected TextureSelect select = new TextureSelect();
	public SpriteBatch batch;
	protected TextureManager textureManager;
	/**
	 * 是否自动执行 tickFrame(). 默认 true 表示自动调用 tickFrame().
	 */
	protected boolean tickAutomatically = true;

	public void initTextures(TextureManager manager) {
		this.textureManager = manager;
		for (TextureDescription description : textures) {
			for (TextureDescription.Sheet sheet : description.sheets) {
				Texture texture = manager.getTexture(sheet.imagePath);

				for (TextureDescription.Region region : sheet.regions) {
					regionMap.put(region, new TextureRegion(texture, region.x, region.y, region.width, region.height));
				}
			}
		}
	}

	/**
	 * 返回现在正在使用的纹理.
	 */
	public TextureDescription.Region getCurrentTexture() {
		if (select.getSequence() == null) {
			return null;
		}
		return select.select();
	}

	/* **********
	 * 标记绘制 *
	 ********** */
	/**
	 * <p>已知的标记绘制有:
	 * <li>1. flash: 无敌时间的白闪, 使用 white tint
	 * <li>2. warn: 预警 (黄描边), 准备在运行中新建一个空白 {@link Texture} 来绘制描边内容, 并用 tint 方式染成黄色
	 * <li>3. attacking: 攻击状态 (红描边), 同上
	 * </li><br>
	 *
	 * <p>2 和 3 不共存时, 3 优先.
	 * 2 里面说到的描边用 textures 由 {@link TextureManager} 保存.
	 */
	public static final String
			EFFECT_FLASH = "flash",
			EFFECT_WARN = "warn",
			EFFECT_ATTACKING = "attacking";

	/**
	 * 白闪无敌开始的时间. 现在没有在白闪的话, 则为 -1. 使用的是 worldTime
	 * <p>至于为什么不使用 activeTime. 你试一下房间切换时受伤就知道了.
	 * 房间切换时 activeTime 不会变, 导致角色一直都是白色.
	 * <p>无敌持续时间时, 每 0.1s 一个来回,
	 * 0 ~ 0.05s 时白色显示, 0.05 ~ 0.1s 不显示
	 * </p>
	 */
	public long flashStartTime = -1;

	public void applyFlash() {
		if (flashStartTime == -1)
			this.flashStartTime = ticker.worldTime;
	}

	public void clearFlash() {
		this.flashStartTime = -1;
	}

	public Sprite getSprite() {
		return sprite;
	}

	public Color stroke;
	public long strokeStartTime;

	public void applyRedStroke() {
		stroke = Color.RED;
		if (strokeStartTime == -1) {
			strokeStartTime = ticker.worldTime;
		}
	}

	public void applyYellowStroke() {
		stroke = Color.YELLOW;
		if (strokeStartTime == -1) {
			strokeStartTime = ticker.worldTime;
		}
	}

	public void clearStroke() {
		stroke = null;
		strokeStartTime = -1;
	}

	/**
	 * 让上层检查白闪、stroke 等其它效果
	 */
	protected void checkEffects() {
		// do-nothing
	}

	/**
	 * 获得纹理的朝向, 是向左还是向右
	 * @return
	 *   是否向右
	 */
	public abstract boolean getOrientation();

	@Override
	public void draw(Ticker ticker) {
		TextureDescription.Region entry = getCurrentTexture();
		if (entry == null) {
			return;
		}
		sprite.setRegion(regionMap.get(entry));

		int widthInPixel = select.getSheet().blockWidth;
		int heightInPixel = select.getSheet().blockHeight;

		float x, // 单位: 像素 -> 格子
		y = getBy() + entry.offsetY / (float) widthInPixel; // 单位: 像素 -> 格子

		float fw = (float) entry.width / widthInPixel,
		fh = (float) entry.height / heightInPixel;

		boolean orientation = getOrientation();
		if (orientation) {
			sprite.setFlip(false, false);
			x = getBx() + entry.offsetX / (float) widthInPixel;
		} else {
			sprite.setFlip(true, false);
			x = getBx() - entry.offsetX / (float) widthInPixel - fw;
		}

		// 绘画
		if (tickAutomatically && !ticker.paused) {
			tickFrame(ticker.delta);
		}
		checkEffects();
		long now = ticker.worldTime;

		// 白闪
		if (flashStartTime > 0) {
			int tDelta = (int) ((now - flashStartTime) % 100);
			if (tDelta >= 50) {
				drawSprite(batch, x, y, fw, fh);
			} else {
				drawWithWhiteTint(batch, x, y, fw, fh);
			}
		} else {
			drawSprite(batch, x, y, fw, fh);
		}

		// 描边
		if (stroke != null) {
			int tDelta = (int) ((now - strokeStartTime) % 100);
			if (tDelta >= 50) {
				drawStroke(batch, x, y, fw, fh);
			}
		}
	}
	
	private void drawWithWhiteTint(SpriteBatch batch, float x, float y, float fw, float fh) {
		batch.begin();
		batch.setBlendFunction(GL20.GL_ONE_MINUS_SRC_ALPHA, GL20.GL_ONE_MINUS_SRC_ALPHA);
		batch.draw(sprite, x, y, fw, fh);
		batch.setBlendFunction(GL20.GL_ONE_MINUS_DST_ALPHA, GL20.GL_DST_ALPHA);
		batch.setColor(1, 1, 1, 0.8f);
		batch.draw(Textures.white, x, y, fw, fh);
		batch.setBlendFunction(GL20.GL_SRC_ALPHA, GL20.GL_ONE_MINUS_SRC_ALPHA);
		batch.setColor(1, 1, 1, 0.4f);
		batch.draw(sprite, x, y, fw, fh);
		batch.setColor(1, 1, 1, 1);
		batch.end();
	}

	private void drawSprite(SpriteBatch batch, float x, float y, float fw, float fh) {
		batch.begin();
		batch.draw(sprite, x, y, fw, fh);
		batch.end();
	}

	private void drawStroke(SpriteBatch batch, float x, float y, float fw, float fh) {
		final TextureDescription.Region region = getCurrentTexture();
		if (region == null) {
			return;
		}
		final TextureRegion strokes = textureManager.generateStrokeTexture(region);
		boolean flipX = sprite.isFlipX(), flipY = sprite.isFlipY();

		batch.setShader(textureManager.alphaToAllShader);
		batch.begin();
		batch.setBlendFunctionSeparate(
				GL20.GL_SRC_ALPHA, GL20.GL_ONE_MINUS_SRC_ALPHA, GL20.GL_SRC_ALPHA, GL20.GL_SRC_ALPHA);
		batch.setColor(stroke);
		batch.draw(strokes,
				flipX ? x + fw : x,
				flipY ? y + fh : y,
				flipX ? -fw : fw,
				flipY ? -fh : fh);
		batch.setColor(1, 1, 1, 1);
		batch.end();
		batch.setShader(null);
	}

	public void tickFrame(float time) {
		select.tick(time);
	}

	/**
	 * 获取 x 坐标. 单位: 块
	 */
	public abstract float getBx();

	/**
	 * 获取 y 坐标. 单位: 块
	 */
	public abstract float getBy();

	/**
	 * @return 是否成功
	 */
	public boolean setState(String stateName) {
		if (select.getSheet().hasState(stateName)) {
			select.setState(stateName);
			return true;
		}

		for (TextureDescription sheet : textures) {
			if (sheet.hasState(stateName)) {
				select.setSheet(sheet);
				select.setState(stateName);
				return true;
			}
		}

		return false;
	}

	public String getState() {
		return select.getState();
	}

	@Override
	public void dispose() {
		// do-nothing
	}
}
